package com.ludomancy.gamelib;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Vector;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Music
{
	private URL url;
	private boolean stop = true;
	private boolean loop = false;
	
	private AudioFormat decodedFormat;
	private AudioInputStream din;
	private AudioInputStream in;
	private SourceDataLine line;
	private boolean isOver = false;
	public Soundtrack listener;
	private static Vector loaded = new Vector();
	private Thread thread;
	
	private byte[] data;
	private ByteArrayInputStream bis;
	
	Music(String path)
	{
		this(path, null);
	}
	
	Music(String path, Soundtrack soundtrack)
	{
		url = GameApplet.gameAppletInstance.getClass().getResource("/"+path);
		this.listener = soundtrack;
		try
		{
			resetInputStreams();
			line = getLine(decodedFormat);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		loaded.add(this);
	}
	
	public void loop()
	{
		if(Sound.soundActivated)
		{
			loop = true;
			play();
		}
	}
	
	public void play()
	{
		stop = false;
		isOver = false;
		resetInputStreams();
//		try
		{
			thread = new Thread()
			{
				public void run()
				{
					internalPlay();
				}
			};
			thread.start();
		}
//		catch(Exception e)
//		{
//			e.printStackTrace();
//		}
		
	}
	
	public void stop()
	{
		stop = true;
		if(thread != null)
		{
			try
			{
				thread.join();
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			thread = null;
		}
	}
	
	public boolean isRunning()
	{
		return !stop;
	}
	
	public boolean loopOver()
	{
		return isOver;
	}
	
	public static Music releaseAll()
	{
		Music running = null;
		for (int i = 0; i < loaded.size(); i++)
		{
			Music m = (Music) loaded.elementAt(i);
			if(m.isRunning())
			{
				running = m;
			}
			m.stop();
			m.release();
		}
		return running;
	}
	
	private void internalPlay()
	{
		try
		{
			byte[] data = new byte[4096];
			if (line != null)
			{
				// Start
				line.start();
				int nBytesRead = 0;
				while (nBytesRead != -1 && !stop && Sound.soundActivated)
				{
					nBytesRead = bis.read(data, 0, data.length);
					if (nBytesRead != -1)
					{
						if(line.isOpen())
						{
							line.write(data, 0, nBytesRead);
						}
					}
					else
					{
						if(loop)
						{
							bis.reset();
							resetInputStreams();
							nBytesRead = 0;
						}
					}
				}
				line.drain();
				isOver = true;
//				line.stop();
				if(listener != null && !stop)
				{
					listener.onLoopOver(this);
				}
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	public void release()
	{
		try
		{
			if(line != null)
				line.close();
			if(din != null)
				din.close();
			if(in != null)
				in.close();
		}
		catch(Exception e){}
	}
	
	private void resetInputStreams()
	{
		if(data != null)
		{
			bis.reset();
			return;
		}
		// Get AudioInputStream from given file.
		try
		{
			in = AudioSystem.getAudioInputStream(url);
			if (in != null)
			{
				AudioFormat baseFormat = in.getFormat();
				decodedFormat = new AudioFormat(
						AudioFormat.Encoding.PCM_SIGNED, baseFormat
								.getSampleRate(), 16, baseFormat.getChannels(),
						baseFormat.getChannels() * 2, baseFormat
								.getSampleRate(), false);
				// Get AudioInputStream that will be decoded by underlying
				// VorbisSPI
				din = AudioSystem.getAudioInputStream(decodedFormat, in);
				// read it all
				byte[] buffer = new byte[4096];
				data = new byte[5000000];
				int bytes = 0;
				int offset = 0;
				while(bytes != -1)
				{
					bytes = din.read(buffer, 0, buffer.length);
					if(bytes > 0)
					{
						System.arraycopy(buffer, 0, data, offset, buffer.length);
						offset += bytes;
					}
				}
				bis = new ByteArrayInputStream(data,0,offset);
			}
		} 
		catch (UnsupportedAudioFileException e)
		{
			e.printStackTrace();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	private SourceDataLine getLine(AudioFormat audioFormat) throws LineUnavailableException
	{
		SourceDataLine res = null;
		DataLine.Info info = new DataLine.Info(SourceDataLine.class,
				audioFormat);
		res = (SourceDataLine) AudioSystem.getLine(info);
		res.open(audioFormat);
		return res;
	}
}
